import requests
import json
import hashlib
from loguru import logger
import base64
import cv2
import math
import random
import time
from typing import List, Dict, Any, Optional, Tuple
class safe99:
    def __init__(self):
        self.proxies={
            "http": "http://127.0.0.1:7890",
            "https": "http://127.0.0.1:7890",
        }
        self.d = "102-63a1d8e874d1bca53c15405b083527ee854a39449de6ebd88744502aab9933bb3017fb68689E9BBCe11384e862c35a98"
        self.appKey = "105c69a8-47c2-4510-8a95-24dfbbec5608"
    
    def set_md5(self,str):
        return hashlib.md5(str.encode("utf-8")).hexdigest()
    def get_img(self):

        headers = {
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Content-Type": "application/json; charset=UTF-8",
            "Origin": "https://aq.99.com",
            "Pragma": "no-cache",
            "Referer": "https://aq.99.com/",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-site",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",
            "X-Requested-With": "XMLHttpRequest",
            "sec-ch-ua": '"Not;A=Brand";v="99", "Microsoft Edge";v="139", "Chromium";v="139"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"Windows"',
        }
        url = "https://captcha.99.com/v1/captcha/get"
        dtime=str(int(time.time() * 1000))
        par = str(
            self.appKey + ":" + dtime + ":Cx89P8PrgnTRCwR0HRCvFKOr04LyEN0BJa8bFuClBpgTKw61KQ"
        )
        sign=self.set_md5(par)
        logger.info(
            f"sign==>{sign}"
        )
        data = {
            "appKey": self.appKey,
            "sessionId": dtime,
            "d": self.d,
            "nonce": dtime,
            "sign": sign,
        }
        data = json.dumps(data, separators=(",", ":"))
        response = requests.post(url, headers=headers, data=data,proxies=self.proxies)

        logger.info(response.json())
        logger.info(f"get img status==>{response.status_code}")
        with open("img.json","w",encoding="utf-8") as f:
            f.write(json.dumps(response.json(),indent=4,ensure_ascii=False))
            logger.info(f"已保存: img.json")
        return response.json(),dtime
    def parse_img_data(self,data):
        # 检查数据是否为None
        if data is None:
            logger.error("传入的数据为None")
            return None, None

        if isinstance(data, str):
            data=json.loads(data)
        else:
            # 确保data是字典类型
            if not isinstance(data, dict):
                logger.error(f"数据类型错误: {type(data)}")
                return None, None

            success=data.get("success")
            message = data.get("message")
            logger.info(f"success==>{success}")
            logger.info(f"message==>{message}")

            # 检查data字段是否存在
            ddata=data.get("data")
            if ddata is None:
                logger.error("data字段不存在或为None")
                return None, None

            token=ddata.get("token","token不存在")
            originalImageBase64=ddata.get("originalImageBase64","originalImageBase64不存在")
            jigsawImageBase64 = ddata.get(
                "jigsawImageBase64", "jigsawImageBase64不存在"
            )
            with open("background.jpg", "wb") as f:
                f.write(base64.b64decode(originalImageBase64))
                logger.info("已保存: background.jpg")
            with open("slider.jpg", "wb") as f:
                f.write(base64.b64decode(jigsawImageBase64))
                logger.info("已保存: slider.jpg")
            secretKey=ddata.get("secretKey","secretKey不存在")
            data_type=ddata.get("type","type不存在")
            logger.info(f"token==>{token}")
            logger.info(f"secretKey==>{secretKey}")
            logger.info(f"originalImageBase64==>{str(originalImageBase64)[:200]}")
            logger.info(f"jigsawImageBase64==>{str(jigsawImageBase64)[:200]})")
            logger.info(f"type==>{data_type}")
            return token, secretKey
    def get_token(self):

        headers = {
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Content-Type": "application/json",
            "Origin": "https://aq.99.com",
            "Pragma": "no-cache",
            "Referer": "https://aq.99.com/",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-site",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",
            "sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Microsoft Edge\";v=\"139\", \"Chromium\";v=\"139\"",
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": "\"Windows\""
        }
        url = "https://aq-tqdna-generator-web-pc-private.99.com/tqdna/api/v1/e-ddna/token/102"
        data = {
            "ap": {
                "ts": 1755225019967,
                "nonce": "118e5e40db6343b6afcde5b859963f71cbd47dfe69664c218b2586e0485b31de115745d041af420425f3e725f2c1b4a5d34581c05634039435e52f34698ef162",
                "sgei": "7fehaz11mqs9azokoijsmk16xb3r1b40ou4m"
            },
            "tc": 0,
            "ext": "",
            "sig": "e63c5b2ecca985bd68faeec6f23e7c59"
        }
        data = json.dumps(data, separators=(',', ':'))
        response = requests.post(url, headers=headers, data=data,proxies=self.proxies)

        print(response.text)
        print(response)
    def get_d(self):

        headers = {
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Content-Type": "application/json",
            "Origin": "https://aq.99.com",
            "Pragma": "no-cache",
            "Referer": "https://aq.99.com/",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-site",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",
            "sec-ch-ua": '"Not;A=Brand";v="99", "Microsoft Edge";v="139", "Chromium";v="139"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"Windows"',
        }
        url = "https://aq-tqdna-generator-web-pc-private.99.com/tqdna/api/v1/e-ddna/generate/102/102-1755225020059f1358e52a85f4f0fbbe21918f942e9f6fad90e23433e4fe09d2d5607f5074c9769c7d333f4c6fad067482fce18f8cde9"
        data = {
            "ap": {
                "ts": 1755225019967,
                "nonce": "118e5e40db6343b6afcde5b859963f71cbd47dfe69664c218b2586e0485b31de115745d041af420425f3e725f2c1b4a5d34581c05634039435e52f34698ef162",
                "sgei": "7fehaz11mqs9azokoijsmk16xb3r1b40ou4m",
            },
            "ver": 1,
            "pt": 2,
            "core": "245b34af5e0674377aed3131de5159ad22192de3f391850540c6f3d27ed558019e484896a233cd49cf17870737aa7a8d600300c4a0f286e68a34c17f8f12420648bb711d4b496bc761202bb916cb3f93cdafffd271b13c9e6201123b88c0c8ff7596fc200112b0a940bc9acfe4d6334add968660227437ed2dce25b409e7caba84d8c8be589662edfdf620a8abf3ba3ebca118232d2f969ec65ad6375ff1dc79c18482825635203a8a74296c57e4374a92887184b06856b779acf71ccbb9c7492103a7a7d6ad02b604eee3e6c6e1fb7fdf9f9f6a977f13760765f3ae284d18351dc9c4dfadcc7a4c2c273925502368be20b5362290f79e200a6c93a6b5287b8e4275f0a320aa2850ee645c625b85a42751b31c6ac879192025607079490e1fb778ae382698df1c30909679ea6b8adf422be95c9b655a813df5f40ba7d0233a76b1f1f3b3cea0a3a4efd43370fb7c2648a69486c7fed4530b26dc014119855a70efa325c24af200256ffe08f2e041bc0009ae88ccba95e55ac6765f6001410242892b517183b5725b64c797e95b8d50172b325df2bac1525ed08ce56650145777da8011653d970d0cd2958e26a2c63e6aba4070329a0851f38dfd6cbf6111d38cff95c2c05ac05971f89b2d11f55bdc39df8f076685053248c12ca18d2b67c2ce898c1a695eac79234eb1f0b3d3a8adc47d0651d0e11c880f17ffc4136c942838b7f2f9c92509082f2d526b58d9a847ebaa0f4b5f2e9eed1af6ef4b33950aea3100f4a9b9e2930af952cf986c95f86c90d5874ed4b9505212084779a7cc15ff0a9d9bd2a689912c245fb63665750fe849aecbe892652425bc0f9be0f6a9827f12ce1f6a829bbe5652a8beec2e95fe0a5dc96e37b8c5d0ca7d98faaab3e264f8e8ffea78645cfce6ab142d2b8b4f9a43f6aef7ad45e9c8298a86df8be194bd9547e81ff368ac225cc47e1f95f8ee3de565bd07c2a3db7200ffd2a5b6898d66013130080d6e26ca26444abc20a7ba653682527645b5e0794995e6de545107700c86cb5d080f1c7feb6dc8dda5379781698538078a53491834cfdbc4fc97abf31ddf138a21ecd5b3dd73af3a5d1fcf135cd713e6cf012b1e61231c8c39e66af6dbe731c402cb5ba953d7e4823a793484871a4a39109e509547483b0188783de81ccb62c0a759b7cb33b53fc4c85856d1ca2ba6c78689a0813bb97720a934326c08db03f4ed7f474c0bb14cd22236ec57cd8ff1f2de3c2a1786703bbae58984e01a02bd6a9bce49d7fcea2d6bcd7553e18073fe0ee8a1009653938dd7d388d4544cbcc5418dd3c29868361c4952744463bc9a7306c581da157582961138bd033f4a159b448ce6930561eed5d5df55d96d52a59b15711b7c0d4fbb5452cad6756b56c91b606adaba53c1a0aefa253b6998a21ce5df6ad2eef12dbadcfb1c881d463fd9d1c0da9a79b12b8a34e2286e51e8ce2b1708f8f1934a2527f06db9a19771ca5ef716cd8cc3ce97e5715d9f237ecd9fd1abf35f160dad0ab2a90086f84a3fbb9bf07929f228decf5bff10c737b35b00e9155ea3135cb05fe7fe4b1ce42e8e8872bd7ec0a7d456d88e56a5d212754193b200d75b25c2ec7cb5bd40c9ff9442867edd2d3b3e752de7d3555bf54b6833e2c8177924a7779a371fbaa0a3e4d05e80b3fcc748911a4ff53f16e66e92c08d9738e2522f407b3cbef7bceebba13b69c3c268472ebae0f3d325167933e216a4506a4655bf1dc912a55bef8a1696958b481e8500cd5ac67c6d8028eaed9bdb9f9a53fe1115512b79da5c33dc210354668f46514a4d19f7dbc0a9980e6acd4db292835ce47599f3ff0b1f97265934dd44e17d15167956ac11501fb6ace51727fdad4f3247bc35f5fd6612ac2ed0d6419e5f6131a6e519cc354e83c8796f7f7490b74860c859b4fe79c621d8f88674823e310995d3f3843ec29a906e2eac5dd4503f5c36e1a6522de583d8a9f14b808ce33cf6eb8d477bee85514e8b3b693962076dc981f25cd3def9140390d25e596d5f74f55945b8190f7e46e5c5a429cdecd14cd118ec6eaa4cdcba42c75b04d05beef2e348be757e1faa08266c4d3ca5a06c14ec034276336312b5d1d2878a04e01a69e7c6e6ff69daec61624c94195c729f6247f5d1d58756b95a8188388e005bb08f086724498179ffb31685b4084e980e954b1b12147c4b73a2b7ff9e5d6a357fa5ee41cfb51ebd95f9dc682583af44f3a5378919e01a958e5a61887d53a3b73447697a3f7fd8f17f66d6b73a0b32a869a7b4b28af443da28659bad798e2091b9eef4fde303e51c485fc03a3fecec20e205bd48176eda1d65679f2b9389a372d65733b374e10028c33dec0f3b8c73fdad257e5da0b2497c381458d2e445b7d4f886a6e3e7701ce88119284b0cacaf38463d891de96f80d83b0def4214a9293d59bc8d56f4fe1ebfd35a047a193112077dfccfb6bc58871fb2e4753618244812edc3633fce71dfc40d9b70ae068604528b769254821aaf255f1df60c69aab73efab93ee4f5eeae5cb8bac394fbbd41f3fc9f6bdcb7dbc6ba1e64242d18c3802861f984718e03b4c49ce17fae0cbe458239f75b504b3fd5ceeefc5c3e842bd8a92b21208cd7836f75e4924459efdf7857b7bd0e6498da8801f8c8cebcdb5dbb4588c99ca62d5baf6968c67f375fada1065a42eb42dcbf5cb9e3a20ac3cf5a5828cd606d297db789ebc99a811db7849b67d9ea517f5bcb22c3a820aea748391b5d212240ffd5a9915ab5f1809302258ef965f797a920c9ceec9abab61fc7b2cb2a6b5b468725e95a512698b43713b629092d2eb6e7d95ded74c4ac5ff8516c08001cc5c9242f7d00f7ec32ecfbfd7a8ac28924f4f5a4cad5bbce0f15e7af65b96253d88395aac2ee0cb203ddda05955a3042cd5f7798337dab0452382b404be365615073241b6d2a55b6e15aa530a9b751a50a8fe7233d9963b407d9b4d697f907f08ca40dda27ef6e497711459632b4ba4d82f487e9f3f3c582c07bc58ee21abad7f64bff42cac8669990fc0bc493be31ca4dd540da33c1260faeb58bcd5ab2b1b90cef551bfdba126f17d2e2578a53c2a6f5f2939b17f1fed5733f03b9dfc0cb93a69c56da7ffccfdd622f16a30d49d3818ac34bddab14acfd6f43c6ba85cb6c78d41d2573a8218d324a1988d9b1ef96abc1f996fa1aaecb89f9cfa3f05d018554bcedfac4599a7d9dffb01fa106efd0df654b132a55cb8d411d4e9799970c5bf80722a66a34508ec47c135a08b6c944effba974a600c3b50b114c533823c4f786964576cc2d25b055347fc2a4b2cb871969cd9f819cbcdc6e56975745a198a1f0bdb9a9b58dafd885f1e2b837ef0b8ab852516841c8614a3b284a2546f22ffbc4ad1c67b9ea180b6048157d1a25b210c9825e0ca920764a77daf9cffbe53073ce2c8b7b0d61163e70715ac2dc700ef0b2845968388f37e1351934a9d795b60f378011c89b8e4f85cf810e7a81b8233f0b61b35a49cd5193e8ab2c8718276f2764b99c0dfb99714fea7a556dd06a04135dc4d3a047a832ddf6dc3da18d8cbde2f02ac7b1d532cb25a08ce80e574652df5cb56a588695a070ace02356e60ed28d3e357b7a0165238c3caabe9d2b1684c8ad0a313c897616a635da1f3f5480cf878536e0237631b2510d07bdc7cc415c41c02be6aae0b1709e439190733f3d588dca3d8e5bd193cb88942758a8f3f462f7de2710ffa2f7a95db5fbaade559eea7315e908bdbdbc1a08121abbf65eedf524d024932c288bb9776600aa49a5ae559516d8df89ce6d2555ce4423c9768808d0739f74060cc2d673d03ce992407c047f419254fdfba42161ed4e26f1a640fced343840e63386c80d29e91110e52d939e522945a8074575dd57d3595e3d3b60ff7bc967785571d214601c2f68d6d79aad73bd6f7bb0da151414ab0cfae90f0b37ed6f052be7558b75899b90ea4cea9064603138818a957ff5837da98e016c8ae73c2f4341928bdad0de579de0d23760ccf5ce46ad46ef1b5d89a8c336b5f46e07bddf34e00062077eee0879defc9b47dde6014003f26a467a697ebb0e1c29dc6ee8d76ae4dc8762baa3d17b6f52e7994c10d65a277b035c85f3eb48276485d047d283f1cb3662ee205411c35c23a05dfaad5d73d16d1b72ce40e64452e513ff73ef62a3a574d9da3475342ec7ab3e42bb38bb27d0114d213a6530b119fe890bd4d9bcb0f6274959d291e8cf36fa440063612ba585b845cef5d90af313fd446cbd02ad8320dd674acab5c19888cad9ef567078938d82083990a13abef3fa4469c083fba5f6b54c1bb86c733ad0b003531d5fdd7d670c7700edc50235d72dd307828615ff1380e6e37d14914aa6c488f734c8e2a280148c2c8f668fd7acf882dbab1a65178438fa6198fbd7246dfb05524083056cb4be220a1167751cbaab6465d95176234af293d711351d707bb623d9f1a69bf2fb167d6a1f22fd8249e922997f340afab03bd6c14098ef2c1a7d6b2c21522541b2df72aea837d2e20d0e0419205cde54a9a04041653ba0e339e99fa56d1e89682b420e529b9e788d189396e0717ea21ffdf429ad9c7e9f77dc56a8db64acb28b1898aaace42059dee5792863e5a80706d5fcf280e59ee1933f9205d5b30a35a3b721eddac8f6457ff1deb05fad11a4d9a123d99b4214cd5d23d57dc479919f10956153f17eab81f175bc49c1f0ce907a8d66b87ff1e5d07ce5e3efd8c3484009861632360c59feb3ca683a579f6231eabb90316769975dd32a117f459f0eb398ebfbeaf0d9b67843d014497488d842ef69eefb8e3290365a5cc3af7692207f085731dc4634bcae01ad799667bc2b25dbe2f045fd94f60746e880d595daaeb8d5cc8b2aba21b0cbb1c8e81304f4f9803ea3c76fe0413dbfd4c119b823b200f7dd8ed64bdf1d9b1a577dee3a45f288d4ac4cd7f2126c0e80e318b015b989ce44a571bd48c56febba94dd222c56f5ea4035bf3b4548b374377122f607cef6364dfb9da05cd966ec9b5b7c2c3920588246f5badfc12f16f0680a76c0cc6096c59349b4228220f8cf430501f80aa3651ca27ddb16f88e1973ee132487faba2e5220cffd1c0901640e2806d8bfde3d8f238905310be4841cad32db7ad5e2ea7e6200cbca90915e719cffb46d7c619d22274d2aae160e17311fc515bdefb42d6813b4bbab5f6111a79bcf65bac6f65efa15c16bc654d3748ff02e33fc74afe81c265aa0280822507b3cdfeabed7905bab6b82ae11fa6ae24d5bba9b901bfe6fc02a752d051892e7c1fbb30d03c71adaf647de5498dbf4983c65814f10864c32ef8cb1454f1218347429c44aafdef94369359804c50cd28ee37c14593fa4974af7464d06e00be833af07d89ba44bf262eb61dd7c7937fc431d25bce7a4818d42460c0be86fe67ec86ff90d5a4486916b74a339480d17788f5e03b92e3703a5430ce9b91ed674e5beb1b346fc6e7775e252c744d7ee5b132e5fb59c94811d4538a007b4708ddbeae9d384a550a2ba7401b2e8e4f89cfc4dba0a29ef90202a8d61f0f262772a13eb6d27d7f9f0b25de27976d47559129f5454af3f2735baa8f8dfdf0b96cccffa67e7cc7f701da0443e4856bb4fb43bc6f8e3d8fab1aa8fac9b81ba31903ca27eb6e5f0fabe647cb50f1be65d399152d6645655f7bfafa4bd5430a572cb49fb2cf72d93b1c125496065e8a6f36a38a7607df9559029b2ebf962bf2dea44ace93040a002f33ec2b8107f84ee540ffea6fda177b55f855b2e6081519caf5756234ef082db61cd912bc21b43a3fcd0943ba9d9edfc7d7f63cd8350099ccd44cbfe5c85fafdd2f1ef9ae8606c0d726ed7c9ee7f36bc1b3b37f04c3f7ba0801bf71a9919ae1db0ec725ab09eecd52be1b2ebf3fce0465644808d3a0d6346b3096ffceeb824a01ac50d6bdda380113e7eb4cb83b02527ef474265ec8c62a7e8765597b9761d588b4a6a46a5804f901ae1ea1259626196b460fa17cfcb7e9fecf156bf7b30bf5ebb710e86aada4edaa80b6a3a1d7bb9a590eb66df035bab58124362dd45957607c52c3a16d2e99dec7ea00c73927bfba7da11bb267187f0c0d30fa33f0b1ed616df0f108f356a6894ba4c4eec1ffa155d2127eea13cc2d7abbc706f41ede4667d1cfce738a55914b78eb604267e13da98c97b5689b082ecdcf1cb882b6b8091db7008a0c76ab077e2d2873ed5f76e4e470217df51bea1d2bcf9577a458ce8bf83dd41ea8c64c733c267f00bf37263dc706b9055c4f7cf8427b90f8f41b85a526d7e336d34b9277f3fd081ac5e89b46d7c458e989670d7fbb39ec80033799d03205dbbdfeee8218d0abad920dcc8607323c608e638060b2e2d8820d3d64049d06f2d7a6964d638ea4150be2af497df6cb97db87253eb4ab9aa1697b15ecbcafb56a8afbe03af4173fed61b5a11bd3c16556ceac11a3d917330b74a38c43e028751b2945707b79b1c6c9f06ffef3660acfb3575b0a1d15e159fa7bb5c29f5277f6c83e78ba56521fecc2e936c6c776c9c5851f998e3e703b423b350510a7607000edad105d91cc5960c28c62cf6c6bd86eab9726daa758bec428bbcc392728e5094da060775972154d16cb8c920228d54fa93673e5a7133dca8f0d612112d4755d3a4d15aede9f3acbf87b03f007a1a511451e8b084efe600dd0013d7c181b391a7e85566ec1aa1ff6c8edd2b9a34f0bf55052b5f51ab6c937fc2f4629f01b0b52bc1ce86f2dfe3d3c6231e0b5ca3f2064ba02448ab32be83caad014f74c860316bf98a93673d70304b77cfadd0be4a50cfeb2756535eb1fa7d175d01f88bad981255d744ba2b7545307818e21fc2f99d7702b7138d15e40b1cb5d1e852c709aa68f8e001e42a1d7a33de77fca98c565676b2e6b9f92eb6e3fd0c28380eed789c2456191c38e8c26138430e5e4f4edf2fa57231ff5b2087507a272cd412cf8956479f5be0d308b006216a3abc09597d3c5774151aa54fb35e5114b9829ba7aa0d9c038c7ae0c8a3b4f743efe6fdd35293dfac6c140b6f31ecb387d4fa0395f212d7d4ce4a040c4815f32145252f8d4cd3c7bc1bca53006f82238261c8197abbd12083aa37dc28941ee36c961e9f6c7bbf7beb5d58565588143ca038fc8196c36f8527fa1423c2a902d774d4288efebc4045352c787b27e6",
            "detail": "0df9f5200bab8338be452bf41a712ba6455daa64645fb5808dc664c70890473cc8266f40883cecd7887160fc295ded922564fe9dfa4196390ff57a8238c9efa4ac63345507f6dd1ebe5ad20e90d3a4c0e54fa345ac7aebaa872099758df14cd97dce1de451c8d951b9fa615e5bb026504d330160b40ae398225ba8c554a3ba38878475b549d07e7f580c71262c779fbf038c9510f4a0e87835a748466eb6c3e548c2ab92dbda246b0ace07b62e5306d697a0ce5fc7c9725fdd7b3d2d937e2d5cc91ac1ab5d712eb074463d8f3c96d568b1301bfe76915541bcf20e2b3b36f9a412d087db2412e8818959b514c24f38d20eaafa9349c1c189f9aee754c75dfbb2430903556d1e16c5c453d3a92e36d5b1f56323b6a69da4331b25918bcd7a43ca589766a9e045f332527e9e6a571e10fa9440f13f165575199a82cc944b603f265d2bee8fad65d3258a3afbfe3304785c92fd758dfe4527ff9da63e43b51463ed66a40fe51eb59516ec0eff88",
            "ext": "",
            "sig": "a3726868fe091200aca2e8548fd41bb9",
            "need_quick_gen_token": True,
            "quick_gen_token_expiration": 300,
            "expected_edna_num": 10,
        }
        data = json.dumps(data, separators=(",", ":"))
        response = requests.post(url, headers=headers, data=data)

        print(response.text)
        print(response)

    def verify(self, sessionId, token, behaviorData, pointJson):
        headers = {
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Content-Type": "application/json; charset=UTF-8",
            "Origin": "https://aq.99.com",
            "Pragma": "no-cache",
            "Referer": "https://aq.99.com/",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-site",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",
            "X-Requested-With": "XMLHttpRequest",
            "sec-ch-ua": '"Not;A=Brand";v="99", "Microsoft Edge";v="139", "Chromium";v="139"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"Windows"',
        }
        url = "https://captcha.99.com/v1/captcha/verify"
        dtime = str(int(time.time() * 1000))
        par = str(
            self.appKey + ":" + dtime + ":Cx89P8PrgnTRCwR0HRCvFKOr04LyEN0BJa8bFuClBpgTKw61KQ"
        )
        sign = self.set_md5(par)
        data = {
            "type": 3,
            "ts": 1216,
            "appKey": self.appKey,
            "nonce": dtime,
            "sign": sign,
            "sessionId": sessionId,
            "token": token,
            "behaviorData": behaviorData,
            "d": self.d,
            "pointJson": pointJson,
        }
        data = json.dumps(data, separators=(",", ":"))
        response = requests.post(url, headers=headers, data=data,proxies=self.proxies)

        logger.info(f"verify==>{response.text}")
        logger.info(f"verify status==>{response.status_code}")
        return response.text

    # ========== 1) 图像识别：返回 {x, y}，y 固定为 5，并保存标注图 ==========

    def identify_gap(self,bg: str, tp: str, out: str) -> dict:
        """
        识别滑块缺口位置，返回 {x, y}，其中 y 固定为 5。
        - bg: 背景图片路径
        - tp: 缺口模板图片路径
        - out: 输出可视化图片路径（在背景图上绘制红框）
        """
        bg_img = cv2.imread(bg)
        tp_img = cv2.imread(tp)
        if bg_img is None:
            raise FileNotFoundError(f"背景图片不存在或无法读取: {bg}")
        if tp_img is None:
            raise FileNotFoundError(f"缺口图片不存在或无法读取: {tp}")

        # 边缘检测
        bg_edge = cv2.Canny(bg_img, 100, 200)
        tp_edge = cv2.Canny(tp_img, 100, 200)

        # 转彩色以与模板匹配流程一致
        bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
        tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)

        # 模板匹配
        res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
        _, _, _, max_loc = cv2.minMaxLoc(res)

        th, tw = tp_pic.shape[:2]
        tl = max_loc
        br = (tl[0] + tw, tl[1] + th)

        # 保存标注图
        vis = bg_img.copy()
        cv2.rectangle(vis, tl, br, (0, 0, 255), 2)
        cv2.imwrite(out, vis)

        # 返回识别坐标
        return {"x": int(tl[0]), "y": 5}

    # ========== 2) 随机轨迹生成（结构一致，可控随机） ==========

    def _ease_out_cubic(self,p: float) -> float:
        return 1 - (1 - p) ** 3

    def _rand_between(self,a: int, b: int) -> int:
        return random.randint(a, b)

    def _choice(self,seq):
        return random.choice(seq)

    def build_random_tq_object(self,
            x: int,
            y: int,
            tqdna: str,
            *,
            start_time: Optional[int] = None,
            duration_ms: Optional[int] = None,
            steps_range: Tuple[int, int] = (120, 220),
            pause_prob: float = 0.15,
            pause_ms_range: Tuple[int, int] = (120, 800),
            micro_pause_prob: float = 0.22,
            micro_pause_ms_range: Tuple[int, int] = (14, 40),
            step_tm_range: Tuple[int, int] = (7, 12),
            tail_micro_steps: int = 14,
            speed_decimals: int = 4,
            with_click: bool = False,
            seed: Optional[int] = None,
            browser: Optional[Dict[str, Any]] = None,
            osinfo: Optional[Dict[str, Any]] = None,
            screen: Optional[Dict[str, Any]] = None,
            viewport: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:

        if seed is not None:
            random.seed(seed)

        # 时间基准
        if start_time is None:
            base_now = int(time.time() * 1000)
            start_time = base_now - self._rand_between(500, 1500)

        # 轨迹点数
        steps = self._rand_between(*steps_range)

        # 起点：目标左上或右上
        horiz_dir = self._choice([-1, 1])
        x_offset = horiz_dir * self._rand_between(200, 900)
        y_offset = -self._rand_between(150, 600)
        x0 = x + x_offset
        y0 = max(0, y + y_offset)

        # tm 分布
        tm_list: List[int] = []
        total_time = 0
        long_pauses_idx = set()
        micro_pauses_idx = set()

        for i in range(steps):
            tm = self._rand_between(*step_tm_range)
            if random.random() < pause_prob and 5 < i < steps - 10:
                tm = self._rand_between(*pause_ms_range)
                long_pauses_idx.add(i)
            elif random.random() < micro_pause_prob and 3 < i < steps - 5:
                tm = self._rand_between(*micro_pause_ms_range)
                micro_pauses_idx.add(i)

            tm_list.append(tm)
            total_time += tm

        if duration_ms is not None and total_time > 0:
            scale = max(0.6, min(1.6, duration_ms / total_time))
            new_tm_list = []
            for i, tm in enumerate(tm_list):
                if i in long_pauses_idx:
                    new_tm_list.append(int(round(tm * scale)))
                else:
                    new_tm_list.append(max(1, int(round(tm * scale))))
            tm_list = new_tm_list

        # 累加时间
        t_values: List[int] = []
        acc = start_time
        for tm in tm_list:
            acc += tm
            t_values.append(acc)

        # 坐标生成：加速-匀速-减速
        xs: List[int] = []
        ys: List[int] = []
        for i in range(steps):
            p_global = (i + 1) / steps
            if p_global < 0.45:
                p_seg = p_global / 0.45
                p = 0.45 * self._ease_out_cubic(p_seg)
            elif p_global < 0.80:
                p_seg = (p_global - 0.45) / 0.35
                p = 0.45 + 0.35 * p_seg
            else:
                p_seg = (p_global - 0.80) / 0.20
                p = 0.80 + 0.20 * (1 - (1 - p_seg) ** 2)

            xi = x0 + (x - x0) * p
            yi = y0 + (y - y0) * (p ** 0.9)
            xs.append(int(round(xi)))
            ys.append(int(round(yi)))

        # 停顿后微抖
        for i in range(steps):
            if i in long_pauses_idx:
                for k in range(1, self._rand_between(2, 4)):
                    j = i + k
                    if j < steps:
                        xs[j] += self._choice([-2, -1, 0, 1, 2])
                        ys[j] += self._choice([-2, -1, 0, 1, 2])

        # 尾部水平微进
        for j in range(steps - tail_micro_steps, steps):
            if j <= 0 or j >= steps:
                continue
            xs[j] = xs[j - 1] + (1 if xs[j] <= xs[j - 1] else 0)
            dy = ys[j] - ys[j - 1]
            if abs(dy) <= 1:
                ys[j] = ys[j - 1]
            else:
                ys[j] = ys[j - 1] + (1 if dy > 0 else -1)

        # 最后一个点锚定
        xs[-1] = x
        ys[-1] = y

        # 速度 s
        trail: List[Dict[str, Any]] = []
        prev_x, prev_y = None, None
        for i in range(steps):
            xi, yi = xs[i], ys[i]
            tm = tm_list[i]
            ti = t_values[i]
            if prev_x is None:
                dist = max(1.0, math.hypot(xi - x0, yi - y0) / max(5, steps))
            else:
                dist = math.hypot(xi - prev_x, yi - prev_y)

            s_val = 0.0 if tm == 0 else round(dist / tm, speed_decimals)
            if tm >= 120:
                s_val = round(self._choice([0.0039, 0.004, 0.0067, 0.0076, 0.0104, 0.0132, 0.0133, 0.0139]), 4)

            trail.append({
                "x": xi,
                "y": yi,
                "t": ti,
                "s": s_val,
                "tm": tm,
                "isd": 0
            })
            prev_x, prev_y = xi, yi

        # hover
        hover_elems = [
            "div.verify-move-block",
            "div.verify-bar-area",
            "div.verify-img-out",
            "div",
            "i.verify-icon.iconfont.icon-right",
        ]
        hover: List[Dict[str, Any]] = []
        hover_count = self._rand_between(8, 12)
        candidate_idx = sorted(random.sample(range(5, steps - 1), hover_count))
        for idx in candidate_idx:
            hover.append({
                "el": self._choice(hover_elems),
                "dur": self._rand_between(3, 48),
                "t": t_values[idx]
            })

        # click（可选）
        click: List[Dict[str, Any]] = []
        if with_click:
            click.append({"x": x, "y": y, "t": t_values[-1] + self._rand_between(100, 2000)})

        # 设备信息
        browser_out = {"name": "Edge", "version": "139.0.0.0"}
        if isinstance(browser, dict):
            browser_out.update(browser)

        os_out = {"name": "Windows", "version": "10"}
        if isinstance(osinfo, dict):
            os_out.update(osinfo)

        screen_out = {
            "resolution": {"width": 1920, "height": 1080},
            "colorDepth": 24,
            "pixelDepth": 24,
            "availableResolution": {"width": 1920, "height": 1032},
        }
        if isinstance(screen, dict):
            screen_out.update(screen)

        viewport_out = {"width": 640, "height": 954}
        if isinstance(viewport, dict):
            viewport_out.update(viewport)

        end_time = t_values[-1] + self._rand_between(0, 5)

        return {
            "trail": trail,
            "click": click,
            "hover": hover,
            "slider": [],
            "startTime": start_time,
            "endTime": end_time,
            "browser": browser_out,
            "os": os_out,
            "screen": screen_out,
            "viewport": viewport_out,
            "tqdna": tqdna,
            "t": end_time
        }

    # ========== 3) 调用本地加密服务（Node.js encrypt-server.js） ==========

    def encrypt_via_server(self,payload: dict, url: str) -> dict:
        """
        调用本地加密服务接口。
        - payload: 需要加密的 JSON 对象（坐标或轨迹）
        - url: 接口地址，如 "http://localhost:3000/encrypt" 或 "/encrypt-trail"
        返回：响应的 JSON（包含 encrypted 等字段）
        """
        try:
            resp = requests.post(url, json=payload, timeout=10)
            resp.raise_for_status()
            return resp.json()
        except requests.RequestException as e:
            raise RuntimeError(f"调用加密服务失败: {e}")

    # ========== 4) 一键流水线：识别 -> 轨迹 -> 两次加密 ==========

    def run_full_pipeline(
            self,
            bg_path: str,
            tp_path: str,
            out_path: str,
            *,
            tqdna: str,
            server_base: str = "http://localhost:3000",
            with_click: bool = False,
            seed: Optional[int] = None
    ) -> dict:
        """
        - 识别图片 -> 得到坐标 {x, y: 5}
        - 用该坐标与 tqdna 生成轨迹对象
        - 调用加密服务两次：
            1) /encrypt  (坐标加密)
            2) /encrypt-trail (轨迹加密)
        返回：{
            "coordinate": {x,y},
            "trail": {...},
            "encrypted_coordinate": {...},
            "encrypted_trail": {...}
        }
        """
        # 1) 识别坐标（并保存标注图）
        coord = self.identify_gap(bg_path, tp_path, out_path)  # {"x": int, "y": 5}

        # 2) 生成轨迹对象（使用识别到的 x，y=5）
        trail_obj = self.build_random_tq_object(
            x=coord["x"],
            y=coord["y"],
            tqdna=tqdna,
            with_click=with_click,
            seed=seed
        )

        # 3) 调用坐标加密接口
        enc_coord = self.encrypt_via_server(coord, f"{server_base}/encrypt")

        # 4) 调用轨迹加密接口
        enc_trail = self.encrypt_via_server(trail_obj, f"{server_base}/encrypt-trail")

        return {
            "coordinate": coord,
            "trail": trail_obj,
            "encrypted_coordinate": enc_coord,
            "encrypted_trail": enc_trail
        }


        



if __name__ == "__main__":
    # 使用示例
    # obj = build_random_tq_object(630, 303, "102-xxxx...", with_click=False, seed=123)
    # print(len(obj["trail"]), "trail points")
    # print(obj["trail"][:5])
    # print(obj["hover"])
    safe=safe99()
    res,sessionId=safe.get_img()
    result = safe.parse_img_data(res)
    if result is not None:
        token, secretKey = result
        # 启动你的 Node 服务: node encrypt-server.js
        # 准备图片路径
        bg = "background.jpg"  # 背景图
        tp = "slider.jpg"  # 缺口模板图
        out = "marked.png"  # 输出标注图

        try:
            result = safe.run_full_pipeline(bg, tp, out, tqdna=safe.d, with_click=False, seed=123)
            logger.info("识别坐标:", result["coordinate"])
            pointJson=result["encrypted_coordinate"]['encrypted']
            behaviorData=result["encrypted_trail"]['encrypted']
            logger.info(f"pointJson==>{pointJson}")
            logger.info(f"behaviorData==>{behaviorData}")
            safe.verify(sessionId,token,behaviorData,pointJson)

        except Exception as e:
            print("流水线执行失败:", e)
    else:
        logger.error("解析图片数据失败")
